import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

import java.sql.SQLException;

import java.util.ArrayList;
import java.util.Collection;

import java.util.concurrent.TimeUnit;

public class SAXHandler extends DefaultHandler implements AutoCloseable {
    private DBConnection connectionVisit;
    private DBConnection connectionVoter;

    private Collection<Visit> visitList;
    private Visit visit;
    private int multiInsertLimit = DBConnection.MULTI_INSERT_LIMIT;

    public SAXHandler() throws SQLException {
        connectionVisit = new DBConnectionVisit();
        connectionVoter = new DBConnectionVoter();

        connectionVisit.fork();
        connectionVoter.fork();
    }

    @Override
    public void close() throws Exception {
        send(true);
        send("", "");   // End of data

        System.out.println();

        connectionVisit.join();
        connectionVisit.close();

        connectionVoter.join();
        connectionVoter.close();
    }

    private void send(boolean forced) throws InterruptedException, SAXException {
        if (visit != null && !forced) {
            if (visitList == null) {
                visitList = new ArrayList<>(DBConnection.MULTI_INSERT_LIMIT);
            }

            visitList.add(visit);
            visit = null;
        }

        if (visitList != null && (forced || visitList.size() >= multiInsertLimit)) {
            send(visitList);
            visitList = null;

            System.out.print(".");
        }
    }

    private void send(Collection<Visit> visitList) throws InterruptedException, SAXException {
        send(
                DBConnection.countVisitsSQL(visitList),
                DBConnection.countVotersSQL(visitList)
        );
    }

    private void send(String sqlVisit, String sqlVoter) throws InterruptedException, SAXException {
        long timer = System.currentTimeMillis();

        send(connectionVisit, sqlVisit);
        send(connectionVoter, sqlVoter);

        timer = (System.currentTimeMillis() - timer) / 1000;

        if (timer > DBConnection.DB_TIMEOUT / 3 && multiInsertLimit > 1) {
            multiInsertLimit /= 2;
        } else if (timer < 2 && multiInsertLimit < DBConnection.MULTI_INSERT_LIMIT) {
            multiInsertLimit *= 2;
        } else {
            return;
        }

        System.out.println(System.lineSeparator() + multiInsertLimit);
    }

    private void send(DBConnection connection, String sql) throws InterruptedException, SAXException {
        if (!connection.getSqlQueue().offer(sql, DBConnection.DB_TIMEOUT, TimeUnit.SECONDS)) {
            throw new SAXException("Send SQL timeout");
        }
    }

    @Override
    public void startElement(String uri, String localName, String qName, Attributes attributes) {
        if ("voter".equals(qName)) {
            try {
                visit = new Visit(attributes.getValue("name"), attributes.getValue("birthDay"));
            } catch (Exception e) {
                e.printStackTrace();
                visit = null;
            }
        } else if ("visit".equals(qName) && visit != null) {
            try {
                visit.setStation(Integer.parseInt(attributes.getValue("station")));

                if (visit.getStation() <= 0) {
                    throw new NumberFormatException("Wrong station number: " + visit.getStation());
                }

                visit.parseTime(attributes.getValue("time"));

                try {
                    send(false);
                } catch (InterruptedException ignored) {}
            } catch (Exception e) {
                e.printStackTrace();
                visit = null;
            }
        }
    }
}
